Crate klvm_traits
source ·Expand description
§KLVM Traits
This is a library for encoding and decoding Rust values using a KLVM allocator. It provides implementations for every fixed-width signed and unsigned integer type, as well as many other values in the standard library that would be common to encode.
As well as the built-in implementations, this library exposes two derive macros
for implementing the ToKlvm
and FromKlvm
traits on structs and enums.
These macros can be used with both named and unnamed structs and enum variants.
§Representations
There are multiple ways to encode a sequence of fields in either a struct or an enum variant.
These are referred to as representations and are specified using the #[klvm(...)]
attribute.
Below are examples of derive macros using each of these representations.
Pick whichever representation fits your use-case the best.
Note that the syntax (A . B)
represents a cons-pair with two values, A
and B
.
This is how non-atomic values are structured in KLVM.
§Tuple
This represents values in an unterminated series of nested cons-pairs.
For example:
()
is encoded as()
, since it’s not possible to create a cons-pair with no values.(A)
is encoded asA
, since it’s not possible to create a cons-pair with one value.(A, B)
is encoded as(A . B)
, since it’s already a valid cons-pair.(A, B, C)
is encoded as(A . (B . C))
, since every cons-pair must contain two values.(A, B, C, D)
is encoded as(A . (B . (C . D)))
for the same reason as above.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(tuple)]
struct Point {
x: i32,
y: i32,
}
let point = Point {
x: 5,
y: 2,
};
let a = &mut Allocator::new();
let ptr = point.to_klvm(a).unwrap();
assert_eq!(Point::from_klvm(a, ptr).unwrap(), point);
§List
This represents values in a null terminated series of nested cons-pairs, also known as a proper list.
For example:
()
is encoded as()
, since it’s already a null value.(A)
is encoded as(A, ())
, since it’s null terminated.(A, B)
is encoded as(A . (B . ()))
, nesting the cons-pairs just like tuples, except with a null terminator.(A, B, C)
is encoded as(A . (B . (C . ())))
for the same reason.
Note that the following code is for example purposes only and is not indicative of how to create a secure program. Using a password like shown in this example is an insecure method of locking coins, but it’s effective for learning.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(list)]
struct PasswordSolution {
password: String,
}
let solution = PasswordSolution {
password: "Hello".into(),
};
let a = &mut Allocator::new();
let ptr = solution.to_klvm(a).unwrap();
assert_eq!(PasswordSolution::from_klvm(a, ptr).unwrap(), solution);
§Curry
This represents the argument part of a curried KLVM program. Currying is a method of partially applying some of the arguments without immediately calling the function.
For example, (A, B, C)
is encoded as (c (q . A) (c (q . B) (c (q . C) 1)))
. Note that the
arguments are quoted and terminated with 1
, which is how partial application is implemented in KLVM.
You can read more about currying on the Chik blockchain documentation.
Note that the following code is for example purposes only and is not indicative of how to create a secure program. Using a password like shown in this example is an insecure method of locking coins, but it’s effective for learning.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(curry)]
struct PasswordArgs {
password: String,
}
let args = PasswordArgs {
password: "Hello".into(),
};
let a = &mut Allocator::new();
let ptr = args.to_klvm(a).unwrap();
assert_eq!(PasswordArgs::from_klvm(a, ptr).unwrap(), args);
§Enums
In Rust, enums contain a discriminant, a value used to distinguish between each variant of the enum.
In most cases, the KLVM representation of the enum will need to contain this discriminant as the first argument.
For convenience, this is the behavior when deriving ToKlvm
and FromKlvm
for enums by default.
§Simple Example
In this example, since the tuple
representation is used and the only values are the discriminants, the variants will be encoded as an atom.
Discriminants default to the isize
type and the first value is 0
. Subsequent values are incremented by 1
by default.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(tuple)]
enum Status {
Pending,
Completed,
}
let status = Status::Pending;
let a = &mut Allocator::new();
let ptr = status.to_klvm(a).unwrap();
assert_eq!(Status::from_klvm(a, ptr).unwrap(), status);
§Custom Discriminator
It’s possible to override both the type of the discriminator, and the value.
The #[repr(...)]
attribute is used by the Rust compiler to allow overriding the discriminator type.
As such, this attribute is also used to change the underlying type used to serialize and deserialize discriminator values.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(tuple)]
#[repr(u8)]
enum Status {
Pending = 36,
Completed = 42,
}
let status = Status::Pending;
let a = &mut Allocator::new();
let ptr = status.to_klvm(a).unwrap();
assert_eq!(Status::from_klvm(a, ptr).unwrap(), status);
§Variant Fields
Of course, you can also include fields on enum variants, and they will be serialized after the discriminator accordingly. It’s also possible to override the representation of an individual variant, as if it were a standalone struct.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(list)]
enum SpendMode {
AppendValue { value: i32 },
#[klvm(tuple)]
ClearValues,
}
let mode = SpendMode::AppendValue {
value: 42
};
let a = &mut Allocator::new();
let ptr = mode.to_klvm(a).unwrap();
assert_eq!(SpendMode::from_klvm(a, ptr).unwrap(), mode);
§Untagged Enums
Often, the discriminator isn’t necessary to encode, and you’d prefer to try to match each variant in order until one matches.
This is what #[klvm(untagged)]
allows you to do. However, due to current limitations, it’s not possible to mix this with #[klvm(curry)]
.
Note that if there is any ambiguity, the first variant which matches a value will be the resulting value.
For example, if both A
and B
are in that order and are the same type, if you serialize a value of B
, it will be deserialized as A
.
use klvmr::Allocator;
use klvm_traits::{ToKlvm, FromKlvm};
#[derive(Debug, PartialEq, Eq, ToKlvm, FromKlvm)]
#[klvm(tuple, untagged)]
enum Either {
ShortList([i32; 4]),
ExtendedList([i32; 16]),
}
let value = Either::ShortList([42; 4]);
let a = &mut Allocator::new();
let ptr = value.to_klvm(a).unwrap();
assert_eq!(Either::from_klvm(a, ptr).unwrap(), value);
Macros§
- Deconstructs a set of curried arguments that has been matched.
- Deconstructs a KLVM list that has been matched.
- Deconstructs a quoted KLVM value that has been matched.
- Deconstructs a KLVM tuple that has been matched.
- Constructs a sequence of nested pairs that represents a set of curried arguments.
- Converts a list of KLVM values into a series of nested pairs.
- Quotes a KLVM value.
- Converts a tuple of KLVM values into a series of nested pairs.
- Creates the type needed to represent a set of curried arguments.
- Creates the type needed to represent a list of KLVM types.
- Creates the type needed to represent a quoted KLVM type.
- Creates the type needed to represent a tuple of KLVM types.
Structs§
- A wrapper for an intermediate KLVM value. This is required to implement
ToKlvm
andFromKlvm
forN
, since the compiler cannot guarantee that the genericN
type doesn’t already implement these traits.